package com.findsing.jetty.websocket.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.websocket.ContainerProvider;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.ws.rs.core.NewCookie;

import org.eclipse.jetty.websocket.jsr356.ClientContainer;

import com.findsing.jetty.websocket.client.endpoints.BroadcastClientEndpoint;
import com.findsing.jetty.websocket.client.endpoints.ClientEndpointConfigurator;
import com.findsing.jetty.websocket.model.Credential;
import com.findsing.jetty.websocket.model.Message;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.client.urlconnection.HTTPSProperties;

public class ClientStarter {
	static final String HOST = "localhost";
	
	static final String WEBSOCKET_PROTOCOL = "wss";
	static final String WEBSOCKET_SERVICE_PORT = "8443";
	static final String WEBSOCKET_SERVICE_CONTEXT_PATH = "/ws";
	static final String WEBSOCKET_SERVICE_BASE_URI =  WEBSOCKET_PROTOCOL + "://" + HOST + ":" + WEBSOCKET_SERVICE_PORT + WEBSOCKET_SERVICE_CONTEXT_PATH;
	
	static final String HTTP_PROTOCOL = "https";
	static final String HTTP_SERVICE_PORT = WEBSOCKET_SERVICE_PORT;
	static final String HTTP_SERVICE_CONTEXT_PATH = WEBSOCKET_SERVICE_CONTEXT_PATH;
	static final String HTTP_SERVICE_BASE_URI =  HTTP_PROTOCOL + "://" + HOST + ":" + HTTP_SERVICE_PORT + HTTP_SERVICE_CONTEXT_PATH;
	
	List<NewCookie> cookies = null;
	
	public static void main(final String[] args) throws Exception {
		ClientStarter client = new ClientStarter();
		
		client.accessFreeContent();
		
		client.loginWithJerseyClient();
		//client.longin();

		client.connectToBroadcaseWebSocket();
	}
	
	/**
	 * WebSocket client
	 * @throws Exception 
	 */
	public void connectToBroadcaseWebSocket() throws Exception
	{
		/**
		 * WebSockt client endpoint:
		 * 
		 * 1) Enable "wss://" protocol
		 */
		System.getProperties().setProperty("org.eclipse.jetty.websocket.jsr356.ssl-trust-all", "true");
		
		/**
		 * 1.1) For bi-direct authentication, setup client end keystore file.
		 *
		 * final String path = ServerStarter.class.getProtectionDomain().getCodeSource().getLocation().getPath();
		 * System.getProperties().setProperty("javax.net.ssl.keyStore", path + "client1");
		 * System.getProperties().setProperty("javax.net.ssl.keyStorePassword", "changeit");
		 */

		final String broadcastServiceUri = WEBSOCKET_SERVICE_BASE_URI + "/broadcast";

		/**
		 * 2) Get WebSocketContainer then connect to server and send message
		 */
		final WebSocketContainer container = ContainerProvider.getWebSocketContainer();

		try {
			Session session = container.connectToServer(BroadcastClientEndpoint.class, URI.create(broadcastServiceUri));
			
			for (int i = 1; i <= 3; ++i) {
				String client = UUID.randomUUID().toString();
				Message message = new Message("Command", "{\"UUID\" :\"" + client + "\"}");
				session.getBasicRemote().sendObject(message);
				Thread.sleep(1000);
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}

		/**
		 * 3)  Application doesn't exit if container's threads are still running
		 */
		((ClientContainer) container).stop();
	}
	
	
	public void accessFreeContent() throws IOException, NoSuchAlgorithmException, KeyManagementException
	{
		final String loginServiceUri = HTTP_SERVICE_BASE_URI + "/home";
		HttpsURLConnection conn = getSSLConnection(loginServiceUri);
		
		conn.setRequestMethod("GET");
		conn.setRequestProperty("Accept", "application/json");
		
		if (conn.getResponseCode() != 200) {
			throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode());
		}
		
		BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

		String output;
		System.out.println("Output from Server ....\n");
		while ((output = br.readLine()) != null) {
			System.out.println(output);
		}

		conn.disconnect();
	}
	
	protected HttpsURLConnection getSSLConnection(String loginServiceUri) throws NoSuchAlgorithmException, KeyManagementException, IOException
	{
		HttpsURLConnection.setDefaultSSLSocketFactory(getSSLContext().getSocketFactory());
		HttpsURLConnection.setDefaultHostnameVerifier(getHostnameVerifier());

		URL url = new URL(loginServiceUri);
		HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

		//SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
		//conn.setSSLSocketFactory(sslsocketfactory);
		
		return conn;
	}
	
	private SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException
	{
		// Create a trust manager that does not validate certificate chains
		TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
			public X509Certificate[] getAcceptedIssuers() {
				return new X509Certificate[0];
			}
			public void checkClientTrusted(X509Certificate[] certs, String authType) {;}
			public void checkServerTrusted(X509Certificate[] certs, String authType) {;}
		} };
		
		SSLContext sc = SSLContext.getInstance("SSL");
		sc.init(null, trustAllCerts, new SecureRandom());
		
		return sc;
	}

	private HostnameVerifier getHostnameVerifier()
	{
		// Ignore differences between given hostname and certificate hostname
		HostnameVerifier hv = new HostnameVerifier() {
			public boolean verify(String hostname, SSLSession session) {
				return true;
			}
		};
		return hv;
	}
	
	/**
	 * Jersey client will automatically wrap/unwrap java class to Json
	 * @throws NoSuchAlgorithmException 
	 * @throws KeyManagementException 
	 */
	public void loginWithJerseyClient() throws KeyManagementException, NoSuchAlgorithmException
	{
		final String loginServiceUri = HTTP_SERVICE_BASE_URI + "/login";

		ClientConfig clientConfig = new DefaultClientConfig();
		clientConfig.getFeatures().put("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
		clientConfig.getProperties().put(
				HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, 
				new HTTPSProperties(getHostnameVerifier(), getSSLContext()));

		Client client = Client.create(clientConfig);
		WebResource webResource = client.resource(loginServiceUri);
		ClientResponse response = webResource
				.type("application/json")
				.post(ClientResponse.class, new Credential("jeff","pass"));

		if (response.getStatus() != 200) {
			throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
		}

		List<NewCookie>cookies = response.getCookies();
		for (NewCookie cookie : cookies) {
			System.out.println(cookie);
		}
		ClientEndpointConfigurator.setCookies(cookies);
		this.cookies = cookies;

		Message message = response.getEntity(Message.class);
		System.out.println("Server response .... \n");
		System.out.println(message);
	}

	public void login() throws IOException, NoSuchAlgorithmException, KeyManagementException
	{
		final String loginServiceUri = HTTP_SERVICE_BASE_URI + "/login_s";
		HttpsURLConnection conn = getSSLConnection(loginServiceUri);

		conn.setDoOutput(true);
		conn.setRequestMethod("POST");
		conn.setRequestProperty("Accept", "application/json");
		if(null != cookies)
		{
			for(NewCookie cookie: cookies)
			{
				conn.setRequestProperty("Cookie", cookie.toString());
			}
		}

		String credential = "{\"userName\":\"jeff\",\"password\":\"pass\"}";
		OutputStream os = conn.getOutputStream();
		os.write(credential.getBytes());
		os.flush();

		if (conn.getResponseCode() != 200) {
			throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode());
		}

		BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));
 
		StringBuilder sb = new StringBuilder();
		String line;
		System.out.println("Server response .... \n");
		while ((line = br.readLine()) != null) {
			sb.append(line);
		}
 
		conn.disconnect();

		System.out.println(sb.toString());
	}

}
